﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Text;

namespace Model
{
    /// <summary>
    /// Concrete class which provides the password generation service
    /// </summary>
    public sealed class PasswordGenerator : BasePasswordGenerator, IPasswordGenerator{}
    public abstract class BasePasswordGenerator
    {
        #region Fields and Ctor
        [Description("Keys reachable with the left hand")]
        protected static readonly string Left = "12345qwertasdfgzxcvb";
        [Description("Keys reachable with the right hand")]
        protected static readonly string Right = "67890yuiophjklnm";
        [Description("Symbols reachable with the left hand")]
        protected static readonly string SymbolsLeft = "!|%";
        [Description("Symbols reachable with the right hand")]
        protected static readonly string SymbolsRight = "^&*()_+{}[]-=:@~';#,./<>?";
        protected static readonly int MaxTrials = 1000;
        protected BasePasswordGenerator()
        {
            MaximumLength = 12;
            MinimumLength = 4;
        }
        #endregion
        #region Exposed members
        [Description("Requested password length must not be less than this value")]
        public virtual int MinimumLength { get; private set; }
        [Description("Requested password length must not be more than this value")]
        public virtual int MaximumLength { get; private set; }        
        [Description("Overrideable method for computing and returning a password")]
        public virtual string GeneratePassword(PasswordGenerationInfo info)
        {
            if (info.Length >= MinimumLength && info.Length <= MaximumLength)
            {
                if (info.UseLeftHand || info.UseRightHand)
                {
                    string stock = BuildStock(info);
                    if (stock.Length > 0)
                    {
                        return CreatePassword(stock, info.Length);
                    }
                }
            }
            return String.Empty;
        }
        #endregion
        #region Implementation details
        /// <summary>
        /// Get the full set of characters to use in generating the password
        /// </summary>
        /// <returns></returns>
        private static string BuildStock(PasswordGenerationInfo info)
        {
            string result = string.Empty;
            if (info.UseLeftHand)
            {
                result += Left;
                result += Left.ToUpper();
                if (info.UseSymbols)
                {
                    result += SymbolsLeft;
                }
            }
            if (info.UseRightHand)
            {
                result += Right;
                result += Right.ToUpper();
                if (info.UseSymbols)
                {
                    result += SymbolsRight;
                }
            }
            return result;
        }
        private static string CreatePassword(string eligibleCharacters, int length)
        {
            int trial = 0;
            string result = String.Empty;
            for (; trial < MaxTrials; trial++)
            {
                List<int> l = new Randomizer().GetRandomInts(eligibleCharacters.Length);
                StringBuilder sb = new StringBuilder();
                for (int i = 0; i < length; i++)
                {
                    sb.Append(eligibleCharacters[l[i]].ToString(CultureInfo.InvariantCulture));
                }
                result = sb.ToString();
                if (IsValidPassword(result))
                {
                    break;
                }
            }
            // if a valid password cannot be derived after 'n' number of trials, 
            // just return what we have
            return result;
        }
        /// <summary>
        /// A valid password will contain at least 1 upper case letter,
        /// at least 1 lower case letter, and at least 1 digit 
        /// </summary>
        /// <param name="candidatePassword"></param>
        /// <returns>true, if the rules are met</returns>
        private static bool IsValidPassword(string candidatePassword)
        {
            char[] c = candidatePassword.ToCharArray();
            if (c.Where(Char.IsUpper).Any())
            {
                if (c.Where(Char.IsLower).Any())
                {
                    if (c.Where(Char.IsDigit).Any())
                    {
                        return true;
                    }
                }
            }
            return false;
        }
        #endregion
        #region Internal Class
        protected sealed class Randomizer
        {
            private readonly Random _rand = new Random();
            public List<int> GetRandomInts(int count)
            {
                var v = Enumerable.Range(0, count).ToArray();
                for (int i = 0; i < count * count; i++)
                {
                    int i1 = _rand.Next(count);
                    int i2 = _rand.Next(count);
                    if (i1 == i2) continue;
                    v[i1] ^= v[i2];
                    v[i2] ^= v[i1];
                    v[i1] ^= v[i2];
                }
                return v.ToList();
            }
        }
        #endregion
    }
}
